﻿/**
* @description  The abstract base class for all slide instances in a slide show.
*/
package com.foed.applications.slideshow.slides {

	import flash.display.Sprite;
	import flash.events.TimerEvent;
	import flash.utils.getDefinitionByName;
	import flash.utils.getQualifiedClassName;
	import flash.utils.Timer;

	import com.foed.animation.transitions.Transition;
	import com.foed.applications.slideshow.SlideProperties;
	import com.foed.applications.slideshow.SlideTransitionFactory;
	import com.foed.applications.slideshow.SlideShow;
	import com.foed.events.SlideShowEvent;
	import com.foed.events.TweenEvent;
	
	public class Slide extends Sprite {
	
		static public var sProperties:SlideProperties;
	
		protected var _time:Number;
		protected var _countdown:Timer;
		protected var _slideData:XML;
		protected var _content:Sprite;
		protected var _transition:Transition;	
		protected var _slideShow:SlideShow;
		protected var _master:Slide;
		protected var _locked:Boolean;

		/**
		* @description  Constructor for abstract base class. This should only be called by child classes.
		*
		* @param  pSlideData  The XML defining a slide instance.
		* @param  pSlideShow  The main slide show application.
		*/
		public function Slide(pSlideData:XML, pSlideShow:SlideShow) {
			init(pSlideData, pSlideShow);
		}
	
		/**
		* @description  Initializes slide and its properties, drawing its background and graphics when necessary.
		*
		* @param  pSlideData  The XML defining a slide instance.
		* @param  pSlideShow  The main slide show application.
		*/
		protected function init(pSlideData:XML, pSlideShow:SlideShow):void {
			_slideData = pSlideData;
			_slideShow = pSlideShow;
			setSlideClassProperties();
			var pWidth:Number = _slideShow.properties.width;
			var pHeight:Number = _slideShow.properties.height;
			x = pWidth/2;
			y = pHeight/2;
			_content = new Sprite();
			_content.x = -pWidth/2;
			_content.y = -pHeight/2;
			var pColor:uint = uint(getSlideProperty(SlideProperties.BG_COLOR));
			_content.graphics.beginFill(pColor, 1);
			_content.graphics.drawRect(0, 0, pWidth, pHeight);
			_content.graphics.endFill();
			addChild(_content);
			_time = getSlideProperty(SlideProperties.TIME);
		}
		
		/**
		* @description  Returns the numeric value for the specified property, as set by the XML.
		*
		* @param  pProperty  The name of the property with the value to retrieve.
		*
		* @returns  The numeric value for the specified property.
		*/
		private function getSlideProperty(pProperty:String):Number {
			var pValue:String = getAttribute(pProperty);
			if (valueIsNull(pValue)) {
				pValue = getClass().sProperties.getProperty(pProperty);
				if (valueIsNull(pValue)) {
					pValue = _slideShow.properties.getProperty(pProperty);
				}
			}
			return Number(pValue);
		}
		
		/**
		* @description  Returns whether the String value passed to the function is null, empty, or not a number.
		*
		* @param  pValue  The value to test for validity.
		*
		* @returns  True if the String value is empty or cannot be cast to a numebr.
		*/
		private function valueIsNull(pValue:String):Boolean {
			return (pValue == null || pValue.length < 1 || isNaN(Number(pValue)));
		}
	
		/**
		* @description  Returns the child class for the calling instance, like TitleSlide or ImageSlide.
		*
		* @returns  The child class reference for the calling instance.
		*/
		public function getClass():Class {
			try {
				var pClassPath:Array = getQualifiedClassName(this).split("::");
				var pFullClassName:String = pClassPath.join(".");
				var pClass:Class = getDefinitionByName(pFullClassName) as Class;
			} catch (e:Error) {
				trace(e.getStackTrace());
			} finally {
				return pClass;
			}
		}

		/**
		* @description  Sets the class level properties for a certain type of slide, like TitleSlide or ImageSlide.
		*/
		private function setSlideClassProperties():void {
			var pClass:Class = getClass();
			if (pClass.sProperties == null) {
				pClass.sProperties = _slideShow.properties.getChildSlideProperties(_slideData.localName());
			}
		}
	
		/**
		* @description  Event handler for when a slide completes its reveal on screen.
		*
		* @param  pEvent  Event fired by Transition.
		*/
		private function onRevealComplete(pEvent:TweenEvent):void {
			_locked = false;
			_transition.removeEventListener(TweenEvent.END, onRevealComplete);
			_transition.die();
			dispatchEvent(new SlideShowEvent(SlideShowEvent.SLIDE_REVEAL_COMPLETE));
		}
	
		/**
		* @description  Event handler for when a timed slide completes its time to display.
		*
		* @param  pEvent  Event fired by Timer.
		*/
		private function onCountdownComplete(pEvent:TimerEvent):void {
			stopCountdown();
			dispatchEvent(new SlideShowEvent(SlideShowEvent.TIMED_SLIDE_COMPLETE));
		}
		
		/**
		* @description  Stops any current timed slide countdown and removes remaining listeners.
		*/
		private function stopCountdown():void {
			if (_countdown) {
				_countdown.removeEventListener(TimerEvent.TIMER_COMPLETE, onCountdownComplete);
				if (_countdown.running) _countdown.stop();
			}
			_countdown = null;
		}
	
		/**
		* @description  Triggers a slide to reveal itself, using its appropriate transition.
		*/
		public function reveal():void {
			_slideShow.addSlide(this);
			_transition = SlideTransitionFactory.getTransition(this);
			_transition.clip = _content;
			_transition.addEventListener(TweenEvent.END, onRevealComplete);
			_transition.start();
		}
	
		/**
		* @description  Returns the value for a specified attribute, as set in the XML slide data.
		*
		* @param  pAttribute  The name of the attribute with the value to retrieve.
		*
		* @returns  The value for the specified attribute.
		*/
		public function getAttribute(pAttribute:String):String {
			var pValue:String = _slideData.@[pAttribute];
			if (pValue.length < 1 && _master) {
				pValue = _master.getAttribute(pAttribute);
			}
			return pValue;
		}

		/**
		* @description  Returns whether there is a subsequent slide to render after the current slide.
		*
		* @returns  True if there is a subsequent slide to render.
		*/
		public function hasNextSlide():Boolean {
			return false;
		}
	
		/**
		* @description  Returns whether there is a previous slide to render before the current slide.
		*
		* @returns  True if there is a previous slide to render.
		*/
		public function hasPreviousSlide():Boolean {
			return false;
		}
	
		/**
		* @description  Causes the previous slide to render. To be overridden by child classes.
		*/
		public function displayPreviousSlide():void {}
	
		/**
		* @description  Causes the next slide to render. To be overridden by child classes.
		*/
		public function displayNextSlide():void {}
	
		/**
		* @description  Begins the Timer countdown for a timed slide.
		*/
		public function startCountdown():void {
			_countdown = new Timer(_time, 1);
			_countdown.addEventListener(TimerEvent.TIMER_COMPLETE, onCountdownComplete);
			_countdown.start();
		}
	
		/**
		* @description  Removes a slide, clears timers and nulls content.
		*/
		public function remove():void {
			stopCountdown();
			_slideShow.removeSlide(this);
			_content = null;
		}
	
		/**
		* @description  Sets the master or parent slide for this instance.
		*
		* @param  pSlide  The slide that contains this instance.
		*/
		public function set masterSlide(pSlide:Slide):void {
			_master = pSlide;
		}

		/**
		* @description  Sets the time that the slide should display for, if timed.
		*
		* @param  pTime  The time in milliseconds that the slide should display for.
		*/
		public function set time(pTime:Number):void {
			_time = pTime;
		}
	
		/**
		* @description  Returns whether this slide should be timed to limit display.
		*
		* @returns  Whether this slide is to be timed.
		*/
		public function get timed():Boolean {
			return _time > 0;
		}
	
		/**
		* @description  Returns whether this slide is currently locked, which means it is in the process of rendering,
		*				and so new slides should not yet be allowed to be navigated to or revealed.
		*/
		public function get locked():Boolean {
			return _locked;
		}
		
		/**
		* @description  Returns the main slide show instance containing this slide.
		*
		* @returns  The main slide show instance containing this slide.
		*/
		public function get slideShow():SlideShow {
			return _slideShow;
		}

		/**
		* @description  Returns the transition data for this slide, to be used to determine its appropriate Transition.
		*
		* @returns  An XMLList containing the transition data for this slide.
		*/
		public function get transition():XMLList {
			return _slideData.child(SlideProperties.TRANSITION);
		}

	}

}